package org.mbds.wolf.android.hce;

import java.io.File;
import java.io.IOException;
import java.util.Map;

import org.mbds.wolf.android.tools.FileUtilities;
import org.mbds.wolf.seql.APDUModel;
import org.mbds.wolf.seql.AppletModel;
import org.mbds.wolf.seql.ISeqlCallBack;
import org.mbds.wolf.seql.ISeqlController;
import org.mbds.wolf.seql.SeqlMetadata;
import org.mbds.wolf.seql.SeqlParser;
import org.mbds.wolf.seql.SeqlWords;
import org.mbds.wolf.seql.SeqlConstants;
import org.mbds.wolf.seql.exceptions.SeqlErrorMsgConstants;
import org.mbds.wolf.tools.BytesTool;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.nfc.cardemulation.HostApduService;
import android.os.IBinder;

/**
 * 
 * @author alesas
 *
 */
public class SeqlHceController  implements ISeqlController, ServiceConnection  {
	
	/** HCE service */
    private String hceServiceName 	= null;
    private IBinder hceService 		= null;
	private static ISeqlCallBack callback 	= null;
	private Context context;
	private File configFile;
	private boolean isServiceConnected;
	
	private static String currentAID = null;

	/**
	 * Constructor
	 * @param context object
	 * @param callback
	 * @throws IOException 
	 */
	public SeqlHceController(Object context, ISeqlCallBack callback, String hceServiceClass) throws IOException 
	{
		this(context, callback, FileUtilities.getFileFromAssets((Context)context, SeqlConstants.DEFAULT_CONFIG_FILE_NAME), hceServiceClass);
	}	
	
	/**
	 * Constructor
	 * @param context object
	 * @param callback
	 * @param config File
	 * @throws IOException 
	 */
	public SeqlHceController(Object context, ISeqlCallBack callback, File config, String hceServiceClass) throws IOException 
	{
		this.hceServiceName = hceServiceClass;
		setCallback(callback);
		this.setContext((Context) context);
		setConfigFile(config);
	}	
	
	/**
	 * initService
	 * @return boolean
	 */
	public boolean initService() 
	{
		Class<HostApduService> c = null;
		
		if(hceServiceName != null) 
		{
		    try 
		    {
		        c = (Class<HostApduService>) Class.forName(hceServiceName);
		    } 
		    catch (ClassNotFoundException e) 
		    {
		        e.printStackTrace();
		        return false;
		    }
		} 
		else 
		{
			return false;
		}
		
		if (!isServiceConnected())
		{
			context.bindService(new Intent(context, c), this, Context.BIND_AUTO_CREATE);
		}
		return true;
	}

	/**
	 * onServiceConnected
	 * @param ComponentName
	 * @param IBinder
	 */
	@Override
	public void onServiceConnected(ComponentName name, IBinder service) 
	{
		this.hceService = service;
		setServiceConnected(true);
		callback.onConnected();
	}

	/**
	 * onServiceDisconnected
	 * @param ComponentName
	 */
	@Override
	public void onServiceDisconnected(ComponentName name) 
	{
		currentAID = null;
		setServiceConnected(false);
		callback.onNotConnected();
	}

	/**
	 * execute
	 * @param SeQL instruction
	 * @param cmdId
	 * @return boolean
	 */
	@Override
	public synchronized boolean execute(final String sqlInstruction, final int cmdId) 
	{
		if (sqlInstruction==null || sqlInstruction.trim().length()==0) 
		{
			return false;
		}
		
		boolean success = false;
		
		try 
		{
			if (!initService())
			{
				return false;
			}
			
	        Map<String, Object> maps = SeqlParser.parseSeql(sqlInstruction,configFile);
	        
			if (maps == null) 
			{
				if (callback!=null)
				{
					callback.onBadRequest(SeqlErrorMsgConstants.MSG_ERR_INCORRECT_SEQL_INS);
				}
			} 
			else 
			{
				AppletModel appletModel = null;
				Object model = maps.get(SeqlWords.STR_APPLET);
				
				if (model instanceof AppletModel) 
				{
					appletModel = (AppletModel) model;
				}
				
				if (appletModel == null) 
				{
					if (callback!=null)
					{
						callback.onBadRequest(SeqlErrorMsgConstants.MSG_ERR_WRONG_CONFIGURATION);
					}
					return false;
				} 		

				if (currentAID==null || !currentAID.equalsIgnoreCase(((AppletModel)model).getAID()))
				{
				
					//select the applet (initialize the result HashMap)
					success = sayHello(((AppletModel)model).getAlias(), cmdId);
					
					if (!success) 
					{
						if (callback!=null)
						{
							callback.onNotConnected();
						}
						return false;
					}
					
				}
				///////////////////////////
				executeCommands(maps, cmdId);
				///////////////////////////
				success = true;
			}
		} catch (Exception e) 
		{
			if (callback!=null) {
				callback.onBadRequest(e.getMessage());
			}
		}
		return success;
	}
	
	/**
	 * sayHello (SELECT AID APDU command ISO 7816-4)
	 * @param appletAlias
	 * @param commandId
	 * @return boolean success
	 */
	@Override
	public boolean sayHello(final String appletAlias, final int commandId) 
	{
		try 
		{
			if (!initService())
			{
				return false;
			}
			AppletModel model = SeqlMetadata.getInstance(configFile).getAppletByAlias(appletAlias);
			currentAID = model.getAID();
			byte[] apdu = APDUModel.getSelectAidApdu(BytesTool.hexStringToByteArray(currentAID));
			
			//////////////////////////////////////////////
			new SeqlProcessApduCommandThread(getContext(), callback, commandId, 0, 1, SeqlWords.STR_APPLET, apdu);
			//////////////////////////////////////////////
			
			return true;
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * executeCommand
	 * @param map of commands
	 * @param command Id
	 */
	public synchronized void executeCommands(final Map<String, Object> mapCmd, final int cmdId) {
		byte[][] commands = (byte[][]) mapCmd.get(SeqlWords.STR_COMMAND);
		int nbCommands = commands.length;
		String[] fields = new String[nbCommands];
		System.arraycopy((String[]) mapCmd.get(SeqlWords.STR_FIELD), 0, fields, 0, fields.length<nbCommands?fields.length:nbCommands);

		for (int i = 0; i < nbCommands; i++) 
		{
			///////////////////////////////////////////
			new SeqlProcessApduCommandThread(getContext(), callback, cmdId, i, nbCommands, fields[i], commands[i]);
			//////////////////////////////////////////
		}
	}

	/**
	 * getContext
	 * @return Context
	 */
	public Context getContext() 
	{
		return context;
	}

	/**
	 * setContext
	 * @param context the context to set
	 */
	private void setContext(Context context) 
	{
		this.context = context;
	}

	/**
	 * isServiceConnected
	 * @return boolean connected
	 */
	@Override
	public boolean isServiceConnected() 
	{
		return isServiceConnected;
	}

	/**
	 * setCallback
	 * @param ISeqlCallBack
	 */
	public void setCallback(ISeqlCallBack callback) 
	{
		this.callback = callback;
	}

	/**
	 * setServiceConnected
	 * @param boolean isConnected
	 */
	private void setServiceConnected(boolean isConnected) 
	{
		this.isServiceConnected = isConnected;
	}

	/**
	 * getConfigFile
	 * @return config file
	 */
	public File getConfigFile() {
		return configFile;
	}

	/**
	 * setConfigFile
	 * @param config file
	 */
	@Override
	public void setConfigFile(File configFile) {
		this.configFile = configFile;
	}

}
